#include "common.h"
#include "sys/stat.h"
#include "linux/sched.h"
#include "linux/kernel.h"
#include "linux/mm.h"

struct m_inode inode_table[NR_INODE] = {{0,},};
static void read_inode(struct m_inode *inode);
static void write_inode(struct m_inode *inode);

static inline void wait_on_inode(struct m_inode *inode)
{
    sysctl_disable_irq();

    while(inode -> i_lock)
    {
        sleep_on(&inode -> i_wait);
    }

    sysctl_enable_irq();
}

static inline void lock_inode(struct m_inode *inode)
{
    sysctl_disable_irq();

    while(inode -> i_lock)
    {
        sleep_on(&inode -> i_wait);
    }

    inode -> i_lock = 1;
    sysctl_enable_irq();
}

static inline void unlock_inode(struct m_inode *inode)
{
    inode -> i_lock = 0;
    wake_up(&inode -> i_wait);
}

void invalidate_inodes(int dev)
{
    int i;
    struct m_inode *inode;

    inode = 0 + inode_table;

    for(i = 0;i < NR_INODE;i++,inode++)
    {
        wait_on_inode(inode);

        if(inode -> i_dev == dev)
        {
            if(inode -> i_count)
            {
                printk("inode in use on removed disk\r\n");
            }

            inode -> i_dev = inode -> i_dirt = 0;
        }
    }
}

void sync_inodes()
{
    int i;
    struct m_inode *inode;

    inode = 0 + inode_table;

    for(i = 0;i < NR_INODE;i++,inode++)
    {
        wait_on_inode(inode);

        if((inode -> i_dirt) && (!inode -> i_pipe))
        {
            write_inode(inode);
        }
    }
}

static int _bmap(struct m_inode *inode,int block,int create)
{
    struct buffer_head *bh;
    int i;

    if(block < 0)
    {
        panic("_bmap: block < 0");
    }
    
    if(block >= (7 + 512 + 512 * 512))
    {
        panic("_bmap: block > big");
    }

    if(block < 7)
    {
        if(create && (!inode -> i_zone[block]))
        {
            if(inode -> i_zone[block] = new_block(inode -> i_dev))
            {
                inode -> i_ctime = CURRENT_TIME;
                inode -> i_dirt = 1;
            }
        }

        return inode -> i_zone[block];
    }

    block -= 7;

    if(block < 512)
    {
        if(create && (!inode -> i_zone[7]))
        {
            if(inode -> i_zone[7] = new_block(inode -> i_dev))
            {
                inode -> i_dirt = 1;
                inode -> i_ctime = CURRENT_TIME;
            }
        }

        if(!inode -> i_zone[7])
        {
            return 0;
        }

        if(!(bh = bread(inode -> i_dev,inode -> i_zone[7])))
        {
            return 0;
        }

        i = ((uint16_t *)(bh -> b_data))[block];

        if(create && (!i))
        {
            if(i = new_block(inode -> i_dev))
            {
                ((uint16_t *)(bh -> b_data))[block] = i;
                bh -> b_dirt = 1;
            }
        }

        brelse(bh);
        return i;
    }

    block -= 512;

    if(create && (!inode -> i_zone[8]))
    {
        if(inode -> i_zone[8] = new_block(inode -> i_dev))
        {
            inode -> i_dirt = 1;
            inode -> i_ctime = CURRENT_TIME;
        }
    }

    if(!inode -> i_zone[8])
    {
        return 0;
    }

    if(!(bh = bread(inode -> i_dev,inode -> i_zone[8])))
    {
        return 0;
    }

    i = ((uint16_t *)bh -> b_data)[block >> 9];

    if(create && (!i))
    {
        if(i = new_block(inode -> i_dev))
        {
            ((uint16_t *)(bh -> b_data))[block >> 9] = i;
            bh -> b_dirt = 1;
        }
    }

    brelse(bh);

    if(!i)
    {
        return 0;
    }

    if(!(bh = bread(inode -> i_dev,i)))
    {
        return 0;
    }

    i = ((uint16_t *)bh -> b_data)[block & 511];

    if(create && (!i))
    {
        if(i = new_block(inode -> i_dev))
        {
            ((uint16_t *)(bh -> b_data))[block & 511] = i;
            bh -> b_dirt = 1;
        }
    }

    brelse(bh);
    return i;
}

int bmap(struct m_inode *inode,int block)
{
    return _bmap(inode,block,0);
}

int create_block(struct m_inode *inode,int block)
{
    return _bmap(inode,block,1);
}

void iput(struct m_inode *inode)
{
    if(!inode)
    {
        return;
    }
    
    wait_on_inode(inode);
    
    if(!inode -> i_count)
    {
        panic("iput: trying to free free inode");
    }
    
    if(inode -> i_pipe)
    {
        wake_up(&inode -> i_wait);

        if(--inode -> i_count)
        {
            return;
        }

        free_page(inode -> i_size);
        inode -> i_count = 0;
        inode -> i_dirt = 0;
        inode -> i_pipe = 0;
        return;
    }
    
    if(S_ISBLK(inode -> i_mode))
    {
        sync_dev(inode -> i_zone[0]);
        wait_on_inode(inode);
    }

    repeat:
        
        if(inode -> i_count > 1)
        {
            inode -> i_count--;
            
            return;
        }
        
        if(!inode -> i_nlinks)
        {
            truncate(inode);
            free_inode(inode);
            return;
        }

        if(inode -> i_dirt)
        {
            write_inode(inode);
            wait_on_inode(inode);
            goto repeat;
        }
    
    inode -> i_count--;
    return;
}

struct m_inode *get_empty_inode()
{
    struct m_inode *inode;
    static struct m_inode *last_inode = inode_table;
    int i;

    do
    {
        inode = NULL;

        for(i = NR_INODE;i > 0;i--)
        {
            if((++last_inode) >= (inode_table + NR_INODE))
            {
                last_inode = inode_table;
            }

            if(!last_inode -> i_count)
            {
                inode = last_inode;
                
                if((!inode -> i_dirt) && (!inode -> i_lock))
                {
                    break;
                }
            }
        }

        if(!inode)
        {
            for(i = 0;i < NR_INODE;i++)
            {
                printk("%04x: %6d\t",inode_table[i].i_dev,inode_table[i].i_num);
                panic("No free inodes in mem");
            }
        }

        wait_on_inode(inode);

        while(inode -> i_dirt)
        {
            write_inode(inode);
            wait_on_inode(inode);
        }
    }while(inode -> i_count);

    memset(inode,0,sizeof(*inode));
    inode -> i_count = 1;
    return inode;
}

struct m_inode *get_pipe_inode()
{
    struct m_inode *inode;

    if(!(inode = get_empty_inode()))
    {
        return NULL;
    }

    if(!(inode -> i_size = get_free_page()))
    {
        inode -> i_count = 0;
        return NULL;
    }

    inode -> i_count = 2;
    PIPE_HEAD(*inode) = PIPE_TAIL(*inode) = 0;
    inode -> i_pipe = 1;
    return inode;
}

struct m_inode *iget(int dev,int nr)
{
    struct m_inode *inode,*empty;

    if(!dev)
    {
        panic("iget with dev == 0");
    }

    empty = get_empty_inode();

    inode = inode_table;

    while(inode < (NR_INODE + inode_table))
    {
        if((inode -> i_dev != dev) || (inode -> i_num != nr))
        {
            inode++;
            continue;
        }

        wait_on_inode(inode);

        if((inode -> i_dev != dev) || (inode -> i_num != nr))
        {
            inode = inode_table;
            continue;
        }

        inode -> i_count++;

        if(inode -> i_mount)
        {
            int i;

            for(i = 0;i < NR_SUPER;i++)
            {
                if(super_block[i].s_imount == inode)
                {
                    break;
                }
            }

            if(i >= NR_SUPER)
            {
                printk("Mounted inode hasn't got sb\r\n");

                if(empty)
                {
                    iput(empty);
                }

                return inode;
            }

            iput(inode);
            dev = super_block[i].s_dev;
            nr = ROOT_INO;
            inode = inode_table;
            continue;
        }

        if(empty)
        {
            iput(empty);
        }

        return inode;
    }

    if(!empty)
    {
        return NULL;
    }

    inode = empty;
    inode -> i_dev = dev;
    inode -> i_num = nr;
    read_inode(inode);
    return inode;
}

static void read_inode(struct m_inode *inode)
{
    struct super_block *sb;
    struct buffer_head *bh;
    int block;

    lock_inode(inode);

    if(!(sb = get_super(inode -> i_dev)))
    {
        panic("trying to read inode without device");
    }

    block = 2 + sb -> s_imap_blocks + sb -> s_zmap_blocks + (inode -> i_num - 1) / INODES_PER_BLOCK;

    if(!(bh = bread(inode -> i_dev,block)))
    {
        panic("unable to read i-node block");
    }

    *(struct d_inode *)inode = ((struct d_inode *)bh -> b_data)[(inode -> i_num - 1) % INODES_PER_BLOCK];
    brelse(bh);
    unlock_inode(inode);
}

static void write_inode(struct m_inode *inode)
{
    struct super_block *sb;
    struct buffer_head *bh;
    int block;

    lock_inode(inode);

    if((!inode -> i_dirt) || (!inode -> i_dev))
    {
        unlock_inode(inode);
        return;
    }

    if(!(sb = get_super(inode -> i_dev)))
    {
        panic("trying to write inode without device");
    }

    block = 2 + sb -> s_imap_blocks + sb -> s_zmap_blocks + (inode -> i_num - 1) / INODES_PER_BLOCK;

    if(!(bh = bread(inode -> i_dev,block)))
    {
        panic("unable to read i-node block");
    }

    ((struct d_inode *)bh -> b_data)[(inode -> i_num - 1) % INODES_PER_BLOCK] = *(struct d_inode *)inode;
    bh -> b_dirt = 1;
    inode -> i_dirt = 0;
    brelse(bh);
    unlock_inode(inode);
}